package com.ustc.hewei.nettyrpc2_0.common.config;

import com.ustc.hewei.nettyrpc2_0.common.enumeration.RpcError;
import com.ustc.hewei.nettyrpc2_0.common.exception.RpcException;
import com.ustc.hewei.nettyrpc2_0.common.utils.Constants;
import com.ustc.hewei.nettyrpc2_0.common.utils.PropertiesUtil;
import com.ustc.hewei.nettyrpc2_0.common.warp.ZkNode;
import com.ustc.hewei.nettyrpc2_0.netty.client.ClientProxy;
import com.ustc.hewei.nettyrpc2_0.netty.client.manager.HandlerManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.x.discovery.ServiceCache;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceInstance;
import org.apache.curator.x.discovery.details.ServiceCacheListener;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author hewei
 * @version 1.0
 * @description: TODO
 * @date 2022/11/24 19:53
 */

@Slf4j
public class ServiceAutowireConfig implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware {
    private final ClientProxy clientProxy;
    private final ServiceDiscovery<ZkNode> serviceDiscovery;
    private final HandlerManager handlerManager;
    private MetadataReaderFactory metadataReaderFactory;
    private final String scannerPackage;

    public ServiceAutowireConfig(ClientProxy clientProxy, ServiceDiscovery<ZkNode> serviceDiscovery, HandlerManager handlerManager) {
        Properties properties = PropertyFactory.getProperties();
        this.clientProxy = clientProxy;
        this.serviceDiscovery = serviceDiscovery;
        this.handlerManager = handlerManager;
        scannerPackage = properties.getProperty(Constants.scannerPackage);
    }


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        // 扫描包中的所有类
        Set<Class<?>> classes = scannerPackages(scannerPackage);
        // 扫描配置的单个类
        classes.addAll(scannerService());
        for (Class<?> clazz : classes) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();

            // 在这里，我们可以给该对象的属性注入对应的实例。
            // 比如mybatis，就在这里注入了dataSource和sqlSessionFactory，
            // 注意，如果采用definition.getPropertyValues()方式的话，
            // 类似definition.getPropertyValues().add("interfaceType", beanClazz);
            // 则要求在FactoryBean提供setter方法，否则会注入失败
            // 如果采用definition.getConstructorArgumentValues()，
            // 则FactoryBean中需要提供包含该属性的构造方法，否则会注入失败
            definition.getConstructorArgumentValues().addGenericArgumentValue(clazz);

            // 注意，这里的BeanClass是生成Bean实例的工厂，不是Bean本身。
            // FactoryBean是一种特殊的Bean，其返回的对象不是指定类的一个实例，
            // 其返回的是该工厂Bean的getObject方法所返回的对象。
            definition.setBeanClass(ServiceFactoryBean.class);

            //这里采用的是byType方式注入，类似的还有byName等
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            beanDefinitionRegistry.registerBeanDefinition(clazz.getSimpleName(), definition);

            serviceWatcher(clazz.getName());
        }
    }

    public void serviceWatcher(String serviceName) {
        ServiceCache<ZkNode> watcher = serviceDiscovery.serviceCacheBuilder().name(serviceName).build();
        try {
            watcher.addListener(new ServiceCacheListener() {
                @Override
                public void cacheChanged() {
                    log.info("服务 {} 发生变动", serviceName);
                    // 异步更新可能会导致顺序错位
                    //executorService.submit(() -> {
                    List<ZkNode> zkNodeList = watcher.getInstances().stream().map(ServiceInstance::getPayload).collect(Collectors.toList());
                    handlerManager.updateCache(serviceName, zkNodeList);
                    log.info("服务 {} 缓存列表已更新", serviceName);
                    //});
                }

                @Override
                public void stateChanged(CuratorFramework curatorFramework, ConnectionState connectionState) {

                }
            });
            watcher.start();
            log.info("服务 {} 监听器已添加", serviceName);
        } catch (Exception e) {
            log.error("监听器添加异常");
            try {
                watcher.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            throw new RuntimeException(e);
        }
        for (ServiceInstance<ZkNode> instance : watcher.getInstances()) {
            handlerManager.connectToProvide(instance.getPayload());
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    private Set<Class<?>> scannerService() {
        String serviceNames = PropertiesUtil.getString(Constants.serviceList);
        if (serviceNames == null) {
            return Collections.EMPTY_SET;
        }
        return Arrays.stream(serviceNames.split(",")).map((a) -> {
            try {
                return Class.forName(a);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                throw new RpcException(RpcError.UNKNOWN_CLASSPATH, a);
            }
        }).collect(Collectors.toSet());
    }

    private Set<Class<?>> scannerPackages(String basePackage) {
        Set<Class<?>> set = new LinkedHashSet<>();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + "**/*.class";
        try {
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    String className = metadataReader.getClassMetadata().getClassName();
                    Class<?> clazz;
                    try {
                        clazz = Class.forName(className);
                        set.add(clazz);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return set;
    }

    protected String resolveBasePackage(String basePackage) {
        if (basePackage == null) return null;
        return ClassUtils.convertClassNameToResourcePath(this.getEnvironment().resolveRequiredPlaceholders(basePackage));
    }

    public Environment getEnvironment() {
        return applicationContext.getEnvironment();
    }

    private ApplicationContext applicationContext;
    private ResourcePatternResolver resourcePatternResolver;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }

    class ServiceFactoryBean<T> implements FactoryBean<T> {
        private final Class<T> interfaceClazz;

        public ServiceFactoryBean(Class<T> interfaceClazz) {
            this.interfaceClazz = interfaceClazz;
        }

        @Override
        public T getObject() throws Exception {
            return clientProxy.getProxy(interfaceClazz);
        }

        @Override
        public Class<?> getObjectType() {
            return interfaceClazz;
        }
    }
}
